package cn.jasonone.tio.tcp.client;

import cn.jasonone.tio.tcp.commons.Command;
import cn.jasonone.tio.tcp.config.constants.CharsetEnum;
import cn.jasonone.tio.tcp.config.properties.ClientProperties;
import cn.jasonone.tio.tcp.model.Data;
import cn.jasonone.tio.tcp.model.Header;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.tio.client.ClientChannelContext;
import org.tio.client.TioClient;
import org.tio.core.ChannelContext;
import org.tio.core.Node;
import org.tio.core.Tio;

import java.io.IOException;
import java.io.Serializable;

/**
 * @author xujie
 * @version 1.0
 * @date 2022/1/19 12:01
 */
@lombok.Data
@Slf4j
public class TcpClient implements InitializingBean, DisposableBean {
    private static final Marker MARKER = MarkerFactory.getMarker("Client");
    private ClientChannelContext channelContext;

    private final TioClient tioClient;

    private final ClientProperties clientProperties;

    /**
     * 发送消息到服务器
     *
     * @param data  数据
     * @param async 是否异步发送
     * @return
     */
    public boolean send(Data data, boolean async) {
        if (!this.isConnected()) {
            throw new RuntimeException(new IOException("请先连接服务器,在进行消息发送"));
        }
        if (async) {
            return Tio.send(channelContext, data);
        } else {
            return Tio.bSend(channelContext, data);
        }
    }

    /**
     * 发送消息到服务器
     *
     * @param command
     * @param body
     * @param async
     * @return
     */
    public boolean send(Command command, Serializable body, boolean async) {
        Data data = new Data(command, body);
        return this.send(data, async);
    }

    /**
     * 发送消息到服务器
     *
     * @param header
     * @param body
     * @param async
     * @return
     */
    public boolean send(Header header, Serializable body, boolean async) {
        Data data = new Data(header, body);
        return this.send(data, async);
    }

    /**
     * 发送消息到服务器
     *
     * @param encryption
     * @param command
     * @param signature
     * @param body
     * @param async
     * @return
     */
    public boolean send(boolean encryption, Command command, String signature, Serializable body, boolean async) {
        Data data = new Data(encryption, command, signature, body);
        return this.send(data, async);
    }

    /**
     * 发送消息到服务器
     *
     * @param bid
     * @param charset
     * @param encryption
     * @param command
     * @param signature
     * @param body
     * @param async
     * @return
     */
    public boolean send(String bid, CharsetEnum charset, boolean encryption, Command command, String signature, Serializable body, boolean async) {
        Data data = new Data(bid, charset, encryption, command, signature, body);
        return this.send(data, async);
    }

    /**
     * 连接服务器,超时时间默认为5s
     *
     * @param ip   服务器IP
     * @param port 端口
     * @throws Exception
     */
    public void connect(String ip, int port) throws Exception {
        this.connect(ip, port, null);
    }

    public boolean isConnected() {
        return this.channelContext != null && !this.channelContext.isClosed && !this.channelContext.isWaitingClose;
    }

    /**
     * 连接服务器
     *
     * @param ip      服务器IP
     * @param port    端口
     * @param timeout 超时时间(单位:秒),如果为null,则默认为5s
     * @throws Exception
     */
    public void connect(String ip, int port, Integer timeout) throws Exception {
        if (!this.isConnected()) {
            log.info(MARKER, "正在连接服务器: {}:{} - timout:{}s", ip, port, timeout);
            if (this.channelContext == null) {
                this.channelContext = this.tioClient.connect(new Node(ip, port), timeout);
            } else {

                this.tioClient.reconnect(this.channelContext, timeout);
            }
        }
    }

    /**
     * 使用配置文件中的参数连接服务器
     *
     * @throws Exception
     */
    public void connect() throws Exception {
        this.connect(clientProperties.getIp(), clientProperties.getPort(), clientProperties.getTimeout());
    }

    /**
     * 停止服务器
     *
     * @throws Exception
     */
    public void disconnect() throws Exception {
        if (isConnected()) {
            this.tioClient.stop();
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (this.clientProperties.isAutoStart()) {
            this.connect();
        }

        if (this.clientProperties.isAutoStop()) {
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                try {
                    destroy();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }));
        }
    }

    @Override
    public void destroy() throws Exception {
        if (this.clientProperties.isAutoStop()) {
            disconnect();
        }
    }
}
